home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C
/
Frameworks
/
Extension Shell 1.5
/
INIT Writing FAQ 1.0.1
< prev
next >
Wrap
Text File
|
1996-04-12
|
66KB
|
1,538 lines
****
**** Please Note
****
**** This may be an old copy of the FAQ. It is only updated
**** as and when Extension Shell is updated. Feel free to
**** Archie for and download newer versions of the FAQ,
**** but please don't email me to let me know - when I
**** bring out a new release of Extension Shell, I'll
**** update the FAQ with the more recent copy.
****
**** -dair
**** dair@kagi.com
****
Answers to Frequently Asked Questions about writing System Extensions on
the Macintosh Computer. Version 1.0.1 11/94
This document is Copyright © 1994 by Brian Stern.
I can be contacted by email at <BrianS@pbcomputing.com> on Internet.
The purpose of this FAQ list is to provide information on the writing of
system extensions. This arcane art is difficult to learn and the existing
information on the subject is spread around in various places. Hopefully
the information here will help new and old INIT writers to write better
INITs in less time.
The questions in this document are broken into two sections: System
Extensions and Trap Patches. While most extensions contain trap patches
these subjects seemed different so I have separated them.
FAQ lists like this one are an integral part of Usenet. When I started
reading Usenet news I assure you that I knew nothing about writing
extensions. I learned by posting questions to comp.sys.mac.programmer and
by reading other's questions and responses. Let me take this space to give
you a few tips on phrasing of questions to newsgroups.
When asking a question try to make the title as descriptive as possible.
Most people don't have time to read every question posted and will ignore
posts whose titles are unclear or meaningless. Don't post questions titled
'HELP, My program doesn't work', or worse 'URGENT NEED HELP with program'.
I'm sure it's urgent to you, but not to me. Always indicate that you're
asking a questing by including a (you guessed it) question mark. Consider
these titles: 'Programmers make big salaries' and 'Programmers make big
salaries?' Don't waste people's time with the former when you mean the
latter. Another way to indicate a question is like this: '[Q] Programmers
make big salaries?'. Try to include one of the words 'who, what, when,
where, why, or how' in the titles of your questions. Your questions are
much more likely to be answered by someone who actually knows the answer if
your title is clear.
The code samples in this document were developed with Think C. You may
need to make some changes to use them with other development systems. I've
used inline assembly in many of the code samples. To my eye this makes
things more clear since it's obvious what's going on. Not everyone agrees
with this and not all development environments provide inline assembly.
It's possible to rewrite most or all of this code by use of inline
functions. If you're writing in Pascal or using CodeWarrior you'll have to
do it that way.
You'll be able to find information on general questions of Mac programming
in the FAQ list maintained by Jon Watte at:
ftp://nada.kth.se/pub/hacks/mac-faq/CSMP_PD_FAQ
If you have questions about writing extensions that aren't addressed in
this document or any comments about it feel free to send them to me. If I
know the answer or can find it out it may find its way into a later
version.
This document may be distributed freely as long as no changes are made to
it. It may not be distributed in ways in which the user must pay for the
document, other than reasonable download costs or costs for the medium,
without the consent of the author.
Thanks to the following people who provided sample code and made
constructive comments: Pete Gontier, Dair Grant, Chelly Green, Devon
Hubbard, Peter Lewis, Jim Walker, Jim Wintermyre.
---------------------------------------------------------------------
System Extension Writing FAQ Outline:
System Extensions:
[1] What is an INIT, exactly?
[2] Should I write an INIT?
[3] Can I write an INIT that makes the application menu into a hierarchical
menu?
[4] Do I need to know assembler to write an INIT?
[5] Can you show me a sample INIT?
[6] Can I have global variables in my INIT?
[7] How do I debug an INIT?
[8] How do I write a Control Panel-INIT combination?
[9] How do I capture keystrokes?
[10] How can my extension get time periodically?
[11] How should an INIT manage memory?
[12] How do I get my INIT to turn itself off?
[13] How do I get my INIT to show its icon like all the other cool INITs
do?
[14] How do I show a dialog from my INIT?
[15] How do I maintain compatibility with future systems?
[16] Any tips for INIT writing?
Trap Patches:
[17] What exactly is a trap patch?
[18] What's the difference between a head patch and a tail patch?
[19] How do I patch a trap?
[20] How do I patch a register-based trap?
[21] Can you show me a tail patch?[22] How do I patch a selector-based
trap?
[23] How do I patch a trap on the PPC?
[24] Can I write a fat trap?
[25] Tips?
[26] What other sources of information are available?
---------------------------------------------------------------------
[1] What is an INIT, exactly?
An INIT is a type of code resource that is loaded into memory and executed
during the startup process. They're called INITs because they're resources
of type 'INIT'. INITs load at the end of the startup process, after the
hardware checks have been done and the system has been started. The span
of time during which INITs load and execute is known as INIT time.
Applications don't load until after INIT time and in most cases the first
application to load is the Finder.
Because they load before all applications, INITs can modify the system in a
way that affects all applications. They can add new functionalities and
modify the way in which many processes occur on the Mac. Apple often
supplies new additions and updates to system software as INITs. This
includes things such as the Drag-and-Drop Manager, Thread Manager, Speech
Manager, and Sound Manager 3.0. At some point these new features are
rolled into a new software release and eventually they are included in new
ROMs, but they all start life as INITs.
Apple recommends that the term 'system extension' be used when
communicating with non-programmers. This name carries the implication that
they extend the functionality of the system. You'll see the terms 'INIT',
'extension', and 'system extension' used to mean the same thing throughout
this document.
Extensions are loaded in a defined order. Resources of type 'INIT' can be
found in files of type 'INIT' (System Extension), 'cdev' (Control Panel),
'RDEV' (Chooser Device), 'appe' (application extension), and 'scri' (script
system extensions). In order to be loaded, the INIT resource must be in
one of these file types in one of several locations in the System Folder.
Under system 7 INITs are loaded first from the Extensions Folder in
alphabetical order. INITs present in script system extension files
(filetype 'scri') load before INITs present in system extension files
(filetype 'INIT'). Next, any INITs in the Control Panels folder are loaded
in alphabetical order. Any INITs present at the root level of the System
Folder are loaded after that. This order of loading is important if an
extension is dependent on the presence of another extension. For example,
if an INIT resource in an INIT file named 'SpeakAll' is dependent on the
presence of the 'Speech Manager' extension, it either needs to have its
name changed to something that sorts after 'Speech Manager' or it has to be
located in the Control Panels folder, since extensions in the Control
Panels folder load after extensions in the Extensions Folder.
In system 6 the Extensions Folder and Control Panels Folders don't exist.
Extensions are simply loaded in alphabetical order from the root level of
the System Folder.
--
[2] Should I write an INIT?
System extensions are not easy to write. If this is your first attempt at
Mac programming, the answer to this question is NO. Many experienced Mac
programmers have never written one and don't intend to.
There are a number of other ways to add functionality without writing an
extension. If you want to add functionality to a single application then
think about writing an FKEY. These are invoked by hitting cmd-shift-number
and perform an action at that time only. The standard screen shot
(cmd-shift-3) is one example.
Another user-invokable means of adding functionality is plug-in modules. A
number of commercial software packages support plug-ins that can add
functionality in a straightforward manner. This includes PhotoShop, Quark
Express, Hypercard and others.
Another means of adding functionality is the use of a background-only
application, AKA faceless-background application (fba). Fbas are
applications without a user interface. One type of fba is known as an
application extension. These are applications whose resource files are of
type 'appe'. They are placed in the extensions folder (or the Startup
Items Folder) and are started up after INIT time. They can communicate
with other processes by Apple Events and by Gestalt selectors. If you
don't need to patch a trap then an fba may be the way to add functionality
to the system. Since an fba runs as a normal process there are some things
that it can do that system extensions cannot do (or at least cannot do
safely), like launch applications and send and receive Apple Events. An
INIT resource in the resource fork of an application extension in the
Extensions Folder will load normally at INIT time. See issue 9 of develop
and the Tech Note PS 2 - Background-Only Applications for more info.
Also consider an application that is started up by being placed in the
startup items folder. The Screen Saver 'Dark Side of the Mac' is written
in this way. Screen Savers are traditionally written as extensions because
extensions can have access to the mouse location and all keyboard events.
However an intelligently written application can do the same and be more
compatible.
If none of these things seems like it will work for you and you are
thinking something like 'I want X to happen every time a resource is
loaded', or otherwise add to or replace the system's functionality in some
way then an extension is probably what you need.
Remember that when developing system extensions you will be working without
a net.
--
[3] Can I write an INIT that makes the application menu into a hierarchical
menu?
Every couple of weeks someone posts a message on comp.sys.mac.programmer
entitled 'Neat idea for an init'. These posts go on to describe some
non-programmer's idea of a great INIT. Invariably these posts fall into
two groups: those that have already been done, and those that should never
be done. Think long and hard about the design of an extension before
starting to write it. Consider your target audience. If it's only
yourself then you can do what you want. My first couple of extensions were
just tests to see if I could actually do it. The answer to the above
question is: Maybe you can but probably you shouldn't.
--
[4] Do I need to know assembler to write an extension?
Yes. Strictly speaking, the simplest extension that just beeps at startup
and doesn't hang around past INIT time requires no assembler. However, any
extension that does anything useful will require some assembler in order to
patch a trap or install a jGNEFilter. The extension can be written mostly
in a high level language like C or Pascal, but there will be bits and
pieces that need to be written in assembler to keep the stack happy or to
access parameters passed in registers. Hopefully there will be enough
sample code in this document to get you on your way if your assembler is
weak.
You will often need to inspect the disassembled source of your extension.
If your development environment doesn't allow this the ResEdit Code Viewer
will also allow you to inspect code resources. It can be found at
ftp://ftp.apple.com/dts/mac/tools/resedit/resedit-extensions.hqx.
--
[5] Can you show me a sample INIT?
Here's the code for the 'Hello World' of INITs.
void main(void)
{
SysBeep( 5 );
}
Not very complicated is it? There are a number of additional things that
are required to make this work:
1) Set the project type to code resource.
2) Set the resource type to 'INIT'.
3) Set the resource ID to something (>= 128), and set the resource name if
desired.
4) Set the resource attributes to 'System Heap'.
5) Set the resource attributes to 'Locked'.
6) Set the filetype of the built code resource file to 'INIT' with any
creator signature.
7) Set the filename of the built code resource file to 'Hello World'.
8) Include a 'sysz' resource indicating a size of 100K.
Let me explain what each of these things does.
1) Because you're building a standalone code resource your compiler needs
to know this. This is how the compiler knows to use A4 addressing, rather
than the A5 addressing used in applications.
2) As mentioned above, extensions are code resources of type 'INIT'.
3) Like any other resources they have IDs and can have names.
4) Setting the system heap flag places the code resource in the system
heap. While not strictly required for this sample, in most other
extensions we will want the code resource to remain in the system heap so
this flag must be set. During the process that loads and executes INITs a
small heap is created. Any memory allocation done by the INIT will, by
default occur in this heap. Also any resources read into memory will, by
default, go into this heap. If the system heap flag isn't set for the INIT
itself then the INIT will be loaded into this temporary heap. When the
INIT exits the heap is disposed and anything in it is lost. If an INIT
intends to stay around past INIT time then it must have the system heap
flag set.
5) The code resource should never move in memory so the Locked bit is set.
When a resource with the Locked bit is loaded into memory it is loaded as
low in the heap as possible. This is what we want since the INIT resource
will never be unlocked. It is poor design to not set the Locked bit and to
rely on the INIT to lock itself. In that case the INIT will not be loaded
low in the system heap. This isn't fatal but will lead to fragmentation of
the system heap.
Some INITs try to use MoveHHi/HLock to move themselves or associated
resources high in the system heap. MoveHHi is disabled for the system
heap. Since the system heap is dynamically sizable the concept of the top
of the heap is invalid. For this reason MoveHHi does nothing when the
handle being referred to is within the system heap.
6) The filetype of 'INIT' gives you the generic extension icon, and of
course allows the extension to load and execute at INIT time.
7) Set the file name to something like 'Hello World'.
8) A 'sysz' resource indicates to the INIT-loading code that your INIT
requires a contiguous block of memory of the indicated size. The system
heap will be expanded if necessary to make certain that a block of the
requested size is available. If no 'sysz' recource is included the system
assumes a block of 16K is sufficient. The format of the 'sysz' resource is
simply a long integer indicating the blocksize. You can create one with
ResEdit, or copy one from another INIT and edit it with the hex editor.
This sample extension requires more than 16K because SysBeep allocates a
sound channel and because you don't know what the size of the 'snd '
resource is for the system alert sound. If no 'sysz' resource has been
included this INIT sometimes doesn't work because there isn't enough room
to allocate the sound channel.
Once these things have been done you can build the code resource and drag
its file to the System Folder. The Finder should place it in the
Extensions Folder for you and it should have the generic extension icon.
When you restart you will hear the beep when the extension is loaded.
Every extension must have a routine called 'main'. When the INIT resource
is loaded into memory during INIT time the system jumps to the beginning of
the code resource. The header of the code resource will normally contain a
branch instruction that branches to the main routine. This routine does
whatever it needs to do and then returns. In this 'Hello World' extension
all that the main routine does is call SysBeep. In more substantial
extensions the main routine will do things like patch traps, install
gestalt selectors, and load resources into memory.
--
[6] Can I have global variables in my INIT?
Yes you can. The Think environment and the CodeWarrior environment use
A4-based addressing for global variables in code resources. The base
address of the extension is found in register A0 on entry to the extension,
and routines are provided to save this value and to set up and restore
register A4 when the INIT is entered later. (In fact the standard header
installed by Think C in code resources loads the address of the code
resource into A0 by use of pc-relative addressing.) See the A4-Addressing
section of your development environment's manual for more information.
When writing extensions with the MPW compilers you may need to use an
A5-based method for accessing global variables. See the Apple tech note
'StandAlone Code, ad nauseam' (#256) for more information on this method.
ftp://ftp.apple.com/dts/mac/tn/platforms.tools.pt/pt-35-stand-alone-code.hqx
and
"Another take on Globals in Standalone Code", Keith Rollin
ftp://ftp.apple.com/dts/mac/docs/develop/develop.12.code/globals-in-standalone-code.hqx
Here is some sample code using the Think C routines to illustrate this:
/****Main*****************************************************/
void
main(void) //Main entry point of the extension
{
void *pToMe;
Boolean initedOK;
pToMe = GetA0(); //Save our address locally
RememberA0(); //Save our base address
SetUpA4(); //Set up A4-based addressing
initedOK = InitAll(); //Patch traps etc.
if ( initedOK )
{
//Make sure we stay around
DetachResource( RecoverHandle( pToMe) );
}
RestoreA4(); //Reset A4 to its value on entry
}
This code sample also illustrates one method extensions can use to remain
in memory after INIT time. Since extensions are code resources they will
be removed from memory when their resource files are closed. This happens
when the main routine exits. To avoid this, DetachResource must be called
with the handle to the code resource. One common mistake with this is not
having the code resource marked system heap. If it's not marked system
heap it will be lost when the temporary heap is destroyed, whether it was
detached or not. The GetA0 routine used above is defined in the tips
section farther down in this document. Here are a few other methods for an
extension to detach itself:
void
main(void) //Another method
{
asm
{
RecoverHandle //A0 already holdss our address
move.L A0, -(A7) //DetachResource is stack-based
DetachResource //so push A0 onto the stack
}
}
void
main(void) //This method uses no assembler
{
DetachResource( Get1Resource( 'INIT', kOurResID ) );
}
--
[7] How do I debug an INIT?
Debugging is generally the most time consuming, difficult, and all-around
pain-in-the-neck part of producing good code. That goes double for INITs.
Unfortunately many parts of INITs must be debugged with low level
debuggers. The low level debuggers that I know about are MacsBug, TMON,
and Jasik's Debugger. MacsBug is free and is available at
ftp://ftp.apple.com/dts/mac/tools/macsbug/macsbug-6-5d6.hqx. The two other
low level debuggers are commercialware. Jasik's debugger has the ability
to do source level debugging of code resources, like system extensions.
In most cases, your low level debugger of choice will load before INIT time
so it can be used to debug initialization of INITs. One exception to this
is Jasik's Debugger, which loads in two parts. One of these is an
extension that must load before your extension.
The debugger can be invoked by calling the Debugger() or DebugStr() traps.
Placing these trap calls in your code will drop you into the low level
debugger so that you can step through your code and inspect registers or
memory as needed.
One useful technique is to take advantage of Macsbug's ability to process
commands after a semicolon in a DebugStr call. The following function can
display information that you would otherwise have to hunt down using hex
offsets:
pascal void SomeFunction (arguments)
{
// ...
asm { MOVE.L fooP, A0 } // fooP can be any type
asm { MOVE.L sizeof(*fooP), D0 }
DebugStr ("\p ; dm rA0 rD0"); // dump (*fooP) in hex
// ...
}
If you haven't placed Debugger() calls in your code then you will have to
set a breakpoint in order to step through any trap patches or other parts
of your extension. Here are a couple of methods to do that. If you have
patched a trap, say GetResource, if you drop into MacsBug and type 'il
GetResource' MacsBug will begin to disassemble at your patch. You can then
set a breakpoint by typing 'br GetResource' or br theaddress' where
theaddress is the address in hex of your patch or some part of it. Type
'brc' when you want to clear all breakpoints. Using the A-trap commands
will also work. 'atb GetResource' will set a break and 'atc' will clear
all A-trap breakpoints.
If you want to set a breakpoint in some other part of your extension, say
in a jGNEFilter, then you need another way of finding its location in
memory. In MacsBug type 'hx syszone^' or just 'hx' to set the current heap
to the system heap (where your extension is located). Type 'br ' (don't
hit return yet) and then type cmd-D to view the names of all the routines
in the system heap that were compiled with MacsBug names turned on (like
yours, right?) Scroll down until you find the name of your routine. Hit
enter and the command line should look like 'br yourRoutineName'. Hit
enter again and the breakpoint will be set. You can also simply type 'br
yourRoutineName' and MacsBug will find it for you. The zone must be set to
the zone containing the routine that you want to break on for MacsBug to
find it, so make sure to set the zone to the system zone.
Identifying your extension in the system heap is dependent on compiling it
with the MacsBug symbols option turned on. This inserts Ascii versions of
the names of each function in the compiled code in such a way the MacsBug ,
and other debuggers can find these names. Jasik's debugger uses a
different method for identifying your code that is based on SYM files,
which are generated by your compiler. The SYM files allows Jasik's
debugger to do source level debugging of INITs and other code resources.
I have used a two-project method, when developing INITs and other code
resources, that helps to cut down debugging time. Any extension consists
of essentially two parts: the initialization portion and the implementation
portion. The initialization portion detaches the resource as described
above, shows the icon, and often patches traps. The implementation portion
contains the actual trap patches. It is quite possible, and desirable, to
test the implementation portion in the context of an application, rather
than as an INIT. To accomplish this your project consists of three parts,
which for a simple case would be three files.
TesterApp Project:
SetUpApplication.c Simple application shell that sets up the trap
patches and provides a mechanism to call the traps
TrapPatches.c Code for the trap patches
INIT Project:
SetUpINIt.c Standard INIT setup; contains main entry
point; patches all necessary traps
TrapPatches.c Code for the trap patches; same file as
in the TesterApp Project
The mechanism for calling the traps in SetUpApplication.c is usually a menu
item. In some cases it might be a dialog with several buttons, each of
which calls a particular trap. It isn't always necessary to patch traps in
your TesterApplication. Simply calling the routines that implement the
guts of the trap patches will often be just as good. Having two projects,
one that builds the tester application and the other that builds the
extension, allows you to save time debugging and to be able to build the
extension at any time.
Writing and testing extensions involves multiple rounds of
'compiling-installing the INIT-rebooting-Stepping through the INIT in a low
level debugger'. Use of the two-project method will cut down this time.
--
[8] How do I write a Control Panel-INIT combination?
System extensions generally have no interface. They do what they do
quietly and the user doesn't want to hear from them. In many cases the
user needs to set certain preferences. The natural mechanism for this is
to couple a control panel with an INIT. The problem of course is how does
the control panel communicate the changes to the INIT. I won't discuss the
general mechanisms of control panel authoring here (see NIM: More Macintosh
Toolbox, Chapter 8), but there are several mechanisms available for
communicating between extensions and control panels.
The simplest mechanism and one that will work in the vast majority of cases
is for the extension to install a Gestalt selector. This selector returns
the address of a block of memory that holds variables that control the
actions of the extension. The control panel calls the Gestalt selector,
retrieves the address of the memory block, and alters the values stored in
the block as needed. In some cases the memory block can contain a function
pointer to a function in the extension that needs to be called from the
control panel. Here is some sample code:
typedef struct CommonInfo{
void (*ResetINIT) (void);//function pointer to reset
//func
Boolean On;
};
#define kSignature 'BLAH' //Should be the sig of the INIT
static CommonInfo gInfo;
/**InstallGestaltSelector****************************************/
//In the INIT; runs at INIT time
OSErr
InstallGestaltSelector(void)
{
OSErr err;
err = NewGestalt( kSignature, OurSelector );
if ( err == noErr )
{
gInfo.ResetINIT = (void *) ResetFunction;
gInfo.On = TRUE;
}
return err;
}
/**OurSelector**************************************************/
//In the INIT
pascal OSErr
OurSelector( OSType theSelector, long *theResponse )
{
SetUpA4();
*theResponse = (long) &gInfo;
RestoreA4();
return noErr;
}
/**ResetFunction**************************************************/
//In the INIT
void
ResetFunction(void)
{
SetUpA4();
//Do something here
RestoreA4();
}
/**Close********************************************************/
//In the Control Panel
void
Close( Boolean IsOn )
{
long result;
CommonInfo *Info;
//Get address of globals struct from init
err = Gestalt( kSignature, &result );
if ( err == noErr )
{
Info = (GlobalsType *) result;
Info->On = IsOn; //Reset OnOff Boolean
( * (*Info).ResetINIT) (); //Jump to INIT
}
}
If it is possible for an error to occur that prevents the extension from
resetting itself the ResetFunction should return an error code and the
control panel should display an alert indicating the problem.
Two additional methods are sometimes used to accomplish communication
between an extension and a control panel:
The first of these is to write a driver that is installed by the extension.
The driver holds global variables and will return the address of the block
of memory holding these variables in response to i/o, status, or control
calls to the driver. Sample code showing how to do this is available in a
package called driver-22 written by Pete Resnick to be found at:
ftp://sumex-aim.stanford.edu/info-mac/dev/src/driver-22-c.hqx.gz
The second additional method is to use the PPC toolbox for direct
communication. There is sample code demonstrating this at:
ftp://ftp.apple.com/dts/mac/sc/7.0.samples/init-cdev.hqx.
--[9] How do I capture keystrokes?
This is accomplished by writing a jGNEFilter function. jGNEFilter
functions are called from GetNextEvent and WaitNextEvent just before those
traps return to an application. They are passed a pointer to the event
record that will be returned to the application. In order to capture
keystrokes the jGNEFilter would simply check the what field of the event
record looking for keyboard events and would extract the information from
the message field if one were found.
The jGNEFilter mechanism is very powerful and is one way that screensavers
can be implemented. The filter would save the time of any keyboard events
and would compare the location of the mouse against its previous location
on null events. If the preset time had elapsed during which no keyboard
events or mouse movement had occurred then the screen saver would activate.
A more complete discussion of the jGNEFilter is in the next section.
If you are thinking of patching WaitNextEvent or PostEvent in order to
capture keystrokes or other events, don't. Use a jGNEFilter instead. It's
easier, it's compatible. It's even documented. See the Tech Note
'GetNextEvent; Blinking Apple Menu' (#85).
ftp://ftp.apple.com/dts/mac/tn/toolbox.tb/tb-11-getnextevent.hqx
--
[10] How can my extension get time periodically?
Installing a jGNEFilter is one method of obtaining periodic time. Since
the jGNEFilter mechanism is dependent on the event processing mechanism,
one problem is that no events may be posted if the mouse is held down for
an extended time.
If your INIT only needs to get time every once in a while then I recommend
that it only do its thing on null events. On other events it should just
return. This will have the least impact on the machine's performance.
Remember that your jGNEFilter will be called for EVERY event on the
machine. Do not do a lot of processing on every event or you will be
slowing down the machine needlessly. Another way to reduce the frequency
of your extension's processing is to use a simple timer, based on
TickCount(). In this way your processing is only done, say, every 30 or 60
ticks, on null events of course.
Sample code demonstrating jGNEFilters can be found in a package called jGNE
Helper by Pete Gontier in the alt.sources.mac archive at:
ftp://ftpbio.bgsu.edu/ftp/pub/alt.sources.mac/vol-01/jgnehelper.cpt.hqx.
Another sample in MPW assembler is at
ftp://ftp.apple.com/dts/mac/sc/snippets/toolbox/jgnefilter.hqx.
Here's another example that works in Think C:
static ProcPtr gOldGNEFilter;
/****InstallFilter***********************************************/
//Run this at INIT time
void
InstallGNEFilter (void)
{
/*Save the ProcPtr to the previous jGNEFilter and insert ours*/
gOldGNEFilter = JGNEFilter;
JGNEFilter = (ProcPtr) StripAddress( FilterProc );
}
/****FilterProc**************************************************/
void
FilterProc(void)
{
EventRecord *theEvent;
long SaveD0;
theEvent = GetA1(); //Move the eventPtr to a variable
SaveD0 = GetD0(); //Preserve D0
SetUpA4();
switch ( (*theEvent).what ) {
case nullEvent:
//Do our thing
break;
case keyDown:
//Do something else
break;
}
//Execute the previous jGNEFilter
SetA1( theEvent ); //Restore A1 for the next jGNEFiler
SetD0( SaveD0); //Restore D0
SetA0( gOldGNEFilter ); //Put next jGNEFilter in A0
RestoreA4();
asm{
Unlk A6
Move.W D0, 4(A7) ;Set Function result on the stack
JMP (A0) ;Jump to the next jGNEFilter
}
}
Since a jGNEFilter has access to the actual event record that will be
returned to the application, the filter can alter the event. The most
common thing to do is to 'cancel' an event by changing it to a null event
(Ex. theEvent->what = nullEvent). In this case it should also set the
value in register D0 to False, or zero.
Here are some additional methods for an extension to get time periodically:
If your extension needs more frequent or regular time then can be provided
by a jGNEFilter then you can install a VBL task or a time manager task.
Since these run at interrupt time they cannot do anything that could move
or purge memory. Other methods include patching a trap that is called
frequently, like SetPort.
A faceless Notification Manager request is another method to get time.
This is a request that has all the fields in the notification record set to
NULL except the nmResp field that holds the address of your routine to be
executed. Your notification response routine will be called soon and will
be able to move memory. If necessary the routine can reinstall itself.
This technique is useful for tasks that need to be executed once or
intermittently. For those tasks that need to be executed regularly use one
of the other techniques.
You could of course have a faceless NM request that is installed by a VBL
task or a time manager task.
Use of a faceless background application in concert with an extension is
yet another method to get time. The fba would do its work on its null
events.
--
[11] How should an INIT manage memory?
In general, at INIT time extensions will want to allocate memory in the
system heap. If you are allocating pointers or handles, use the SYS
variants, like NewHandleSys() and NewPtrSys(). It is a common error to
forget this and then to wonder why the INIT crashes. If you call NewHandle
at INIT time, the handle will be allocated in the temporary heap allocated
for your extension. If your INIT attempts to use it after INIT time the
temporary heap will be long gone, along with any handles or pointers that
had been allocated in it. Any attempt to use handles or pointers that no
longer exist are predictably unpredictable :-)
NewHandle and NewPtr will allocate their memory blocks in the system heap
if the zone has been set to the system heap, as shown in the next
paragraph.
If you need to read in resources you can ensure that they go into the
system heap with something like the following code:
THz saveZone = GetZone();
SetZone( SystemZone() );
//read in resources
SetZone( saveZone );
You will of course need to detach the resources if they need to remain in
memory after your extension exits. The resource file containing your
extension will be closed when your extension exits.
If you need to read resources into memory after INIT time you need to
decide which heap they should go into, either the application heap or the
system heap. It's a bit hard to make a specific recommendation on this but
if the resource is something that the application is expecting to be read
in then it should go into the application heap. This would include things
like WIND resources in a trap patch to GetNewWindow(). The problem of
course is that the application heap may not have enough room. However, if
the resources are private to the extension then they should go into the
system heap.
It should go without saying that you should always check the error codes on
memory allocating calls and resource manager calls. If your extension is
doing something in response to a user action, say making a network
connection, then it is appropriate to report such errors. In many cases
however, your extension will simply do nothing in the case of out of memory
errors or missing resource errors. It is best to attempt to allocate all
of these things at INIT time and if unsuccessful to bail out then.
As mentioned above, MoveHHi doesn't work in the system heap. If you intend
to allocate a handle that will remain locked for extended periods, then
call ResrvMem before allocating and locking the handle. This will place
the handle low in the system heap and help to prevent heap fragmentation.
Many toolbox calls are documented as not moving or purging memory and as
being safe to call at interrupt time. If you are patching one of these
traps then you must preserve this property. You are guaranteed to cause
other software to crash if you don't, and your users will hate you (once
they figure out that it's you). Be aware that the only memory manager
routine safe to call under these circumstances is BlockMove. Also be aware
that it is unsafe to access an unlocked handle at interrupt time. It is
possible that the memory manager is in the midst of moving it from one
place to another in the heap, and the master pointer may not be updated
yet.
--
[12] How do I get my INIT to turn itself off?
An extension might want to turn itself off at INIT time based on its
preferences setting, or based on a key being pressed or the mouse button
being pressed, or due to an error during initialization. Extensions
usually show an icon with a red X through it in this case. An extension
might also want to turn itself off temporarily after INIT time in response
to its Control Panel. For instance GateKeeper has an on/off switch that
turns off virus checking for a set time.
The strategy for temporarily turning off an extension is simply to set a
global flag and to check it from within the trap patches or other parts of
the extension. If the flag is off then the trap patch simply executes the
previous trap. It is generally unsafe to unpatch or patch traps after INIT
time from an extension. The reason for this is that if another extension
patches the same trap after you have, then it will be jumping to your patch
when it has completed its work. If you have removed your patch then this
calling chain will be disrupted and bad things will happen. Also, the
Finder patches various traps when it loads, which is after INIT time.
Disrupting those patches would be a very bad thing.
If an extension determines at INIT time that it isn't going to stay around
then it shouldn't call DetachResource on itself. It's best that your
extension determine that anything it's dependent on, such as resources,
specific system Managers, and sufficient memory, are present *before* it
starts to patch traps and install drivers, jGNEFilters and so on. It would
be a very bad idea for an extension to not detach itself after it had
already patched a trap if the trap patch resided in the extension.
Many extensions use a particular key press as a signal to indicate that the
user wants them not to run. Of course the system uses the shift key as a
signal not to turn on any extensions so you can't use that. Some
extensions use the option or command keys for this purpose. The problem
with using those keys is that every time I rebuild the desktop those
extensions are needlessly inactivated. I recommend that the space bar be
used for this purpose. Here's some sample code:
Boolean
SpaceBarIsDown(void)
{
KeyMap theKeys;
GetKeys( theKeys );
if ( theKeys[1] & 0x00000200 )//Check for spacebar
return TRUE;
else
return FALSE;
}
--
[13] How do I get my INIT to show it's icon like all the other cool inits
do?
This is easy. There is code available that does this for you. Get a
package written by Jim Walker called ShowIcon7 at:
ftp://mac.archive.umich.edu/mac/development/source/showicon7.sit.hqx
You use it essentially as a plug-in. Just pass in the icon's resource ID
and it does everything for you. It also shows how to set up an A5 world in
an extension. You might also take a look at Dair Grant's Extension shell
package. This one shows how to do animated icons.
If your extension decides that it can't install itself then it passes the
resource ID of an icon that has a red X through it to the showicon7 code
resource.
Some extensions include the ShowIcon code within their own code resources.
This code is only about 1K but it seems pointless to me for this code to
sit in the system heap when it doesn't have to be. Use it as a plug-in for
your extensions.
--
[14] How do I show a dialog from my INIT?
First of all, I hate windows of any kind during the startup process. If
all you want to do is show an alert then use the Notification Manager.
Your alert will show up when the Finder starts but the user will see it and
will get whatever message you need to send.
The problem I have with windows at INIT time is that they slow down this
process and require user interactivity in a process that shouldn't do so.
Consider the computer that is on 24 hours a day doing something important
unattended. The power goes off and when it comes back on the machine
reboots. The user returns several hours later to find that the machine is
still in the middle of its startup because your alert is waiting for a
response. Consider also the hapless INIT writer who has to reboot his
machine 20 times a day. Your INIT will not last long on my, um, his
machine.
One additional annoyance is that you need to call InitWindows to show your
window and this erases all the nice INIT icons on the screen. I hate that.
Here are a few possible alternatives.
* Play a sound or use the Speech Manager to communicate the information.
* Show a different icon during startup to indicate an error. An icon with
a red X through it is one way to do this. You could also use animated
icons.
* If you must put up an alert then use a timer so that the alert goes away
by itself, even if the OK button isn't clicked.
* If you need to interact with the user to get some information, say a
password for a network connection, then do this once and save the results
in a preferences file. Provide a Control Panel to change the information.
Think of Control Panels as the interface for extensions.
* Store descriptions of any errors that occur in a preferences file. Have
the Control Panel display this information. Remember to clear this
information on each restart and to indicate to the user by sound or icon
that an error has occurred.
* If you need to communicate error information to the user after INIT time
then you should definitely use the Notification Manager. For example,
MacSLIP uses the NM to show an alert indicating that the carrier has been
lost.
If you still want to show a dialog at INIT time then you need to set up an
A5 world first. The ShowIcon7 code mentioned above shows how to do this.
Also see the Tech Note 'Stand-Alone Code' (#256) for an explanation and
sample code for this and 'Giving the (Desk)Hook to INITs' (247) discusses a
bug that can appear when showing windows from extensions.
ftp://ftp.apple.com/dts/mac/tn/operating.system.os/os-02-deskhook-and-init.hqx
If you do use the Notification Manager from an extension to indicate that
the extension couldn't load you should use a self-disposing Notification
request. There are several strategies for doing this. I have some code
samples for this that will be (have been?) posted to alt.sources.mac soon.
--
[15] How do I maintain compatibility with future systems?
This is tough, and the short answer is that you probably don't. You'll
notice that Apple comes out with new versions of its extensions with each
revision of the system. Apple's extensions also eventually disappear as
their functionality is rolled into the system. In all likelihood you'll
have to come out with new versions of your extensions as new versions of
the system come out as well.
Having said all that, there are things you can do to minimize this problem.
Here are a few suggestions:
* Minimize your reliance on undocumented features of the system.
* Minimize your reliance on low memory globals. Use the Universal Header
access 'functions' for accessing the low memory globals if necessary.
* Use system features like Gestalt, the Process Manager, and the
Notification Manager to get information about the system, and to
communicate with the user.
* Try to patch as few traps as possible and use non-patching methods
whenever possible (e.g., use a jGNEFilter instead of patching
WaitNextEvent; the filter is more likely to remain compatible than your
patch).
* Don't use self-modifying code. Self-modifying code changes the
instructions from what they were compiled as, to something else, at
run-time. The classic example is to change the address in a JMP
instruction at run-time so that it jumps to the address of the previous
trap. This may seem faster than using a global variable but it is only
slightly faster. It is harder to write, debug, and maintain self-modifying
code, and it is definitely more likely to break with new system releases.
If you do use self-modifying code, remember to flush the cache. See the
tech note 'Cache As Cache Can' (#261) for more information on that subject.
* Use the Universal Headers for writing your extensions. This will help to
ease the transition when it comes.
--
[16] Any tips for INIT writing?
You may not want your INIT to actually do anything until after INIT time.
You can find the end of INIT time if you have a jGNEFilter installed when
the first null event occurs. The Notification Manager doesn't usually
start processing requests until INIT time is over so posting a faceless
notification request is another method to find the end of INIT time
(probably the best if you don't need a jGNEFilter for something else). You
can also patch Launch to find the end of INIT time but this is trickier.
Unfortunately some extension writers do put up windows during INIT time.
Because of this it's possible that events will occur before INIT time is
over. To fail-safe the above approaches you also need to check for the
presence of the Process Manager with a Gestalt call. The Process Manager
isn't available until after INIT time. If Gestalt reports that the Process
Manager is not available then you need to reinstall your NM request, or
simply wait for another event to be reported to your jGNEFilter when the
Process Manager is available.
Here are some utility routines that can reduce the need for 68K assembler
in your extensions:
pascal void SetA0( void* ) = { 0x205F };
pascal void SetA1( void* ) = { 0x225F };
void * GetA0( void ) = { 0x2008 };
void * GetA7( void ) = { 0x200F };
-----------------------------------------------------------------------
Trap Patches:
--
[17] What exactly is a trap patch?
A trap patch is a method for changing the functionality of a trap. The
addresses of all the traps are maintained in the two trap dispatch tables.
By using the routine NSetTrapAddress and friends you can change the address
of a particular trap to code that you provide. When this is done at INIT
time, all calls to the patched trap from all applications will go to your
code, which in most cases will do something and then call through the
existing trap in the ROMs. Be warned that the Finder patches some traps
when it starts up in a way that prevents previously-installed patches from
executing.
I recommend that you read the descriptions of the trap dispatch mechanism
in the 'Using Assembly Language' chapters in IM I and IV and also in the
'Trap Manager' chapter in NIM Operating System Utilities.
Traps come in several types based on their parameter passing conventions.
Most toolbox traps use pascal calling conventions, which means that all
parameters are passed on the stack and the return value, if any, is placed
on the stack.
Some traps use register-based calling conventions. In these traps the
parameters are passed in registers and the return value is returned in a
register, usually D0. For example all the Memory Manager traps are
register-based and the File Manager traps are also register-based.
Some traps are selector-based. There are only so many spots in the
trap-dispatch tables. In order to preserve space in these tables
selector-based traps have been developed. In these traps a single trap
serves as the front end for a number of system routines. The parameters of
these traps are passed in the usual manner, either on the stack or in
registers, and a selector is also passed, usually in a register. When the
trap is called it checks the selector and then dispatches to the
appropriate routine. Patching each of these types of traps involves
different mechanisms. We'll look at samples of each one.
Patching traps on the PowerMac is a bit different than on the 68K Macs.
Most of the discussion here is aimed at patching traps on the 68K Macs.
Hopefully I'll learn some more about this subject soon and there will be
some better info here on patching traps on the PowerMac.
--
[18] What's the difference between a head patch and a tail patch?
It is most common to add some functionality to a trap when patching it
rather than just replacing the existing trap. For instance I've written an
extension that speaks the text in alerts by using the Speech Manager. This
works by patching Alert and friends. When Alert is called the patch gets
the text that appears in the alert and passes it to the Speech Manager.
The patch then calls the existing Alert trap that is in the ROMs. A patch
that works in this way is called a head patch; it does its business and
then it calls the previous trap.
A tail patch is a bit different. A virus-checking program might want to
patch GetResource and then examine the resource that was read in to see if
it contains a virus. In order to do this the patch must first call the
existing trap and then do its processing, and finally return to the
application. This is a tail patch because some processing occurs after the
existing trap is called. In order for a patch to be a head patch you must
use a jmp instruction to jump to the previous trap. If you use a jsr or a
C function pointer to jump to the previous trap you have a tail patch.
The reason that this head and tail patch business has been so important in
the past is because of an Apple invention called the come-from patch.
Patches were invented, of course, so that Apple could fix bugs in the ROMs
and could update the routines in the ROMs with software. This is why you
can run system 7 on a Mac Plus, whose ROMs are obviously missing most of
the additions made to the toolbox in recent years.
Certain ROM routines are particularly large so it is inconvenient to patch
them if they have bugs in them. To get around this problem the Apple
programmers searched for smaller routines that are called from the large
buggy routines and placed patches in the smaller routines. These patches
check the return address on the stack. If it is the address of the buggy
routine then a fix is applied. If not then they just go on as usual. The
patches to these smaller routines are known as come-from patches. If you
tail patch one of these and then call the existing come-from patch, the
return address on the stack will be in your patch and not the buggy routine
that called you. In this case the come-from patch will not apply its fix
and your system will crash.
The good news is that as of system 7 it is safe to apply tail patches. The
come-from patches still exist in system software, but NGetTrapAddress has
been modified to return an address that is safe to use when applying
tail-patches. However, if you wish your extension to run in System 6 then
tail-patches are not allowed. This is documented in the Trap Manager
chapter in NIM: OS Utilities.
If you absolutely positively need a tail patch in System 6 then the
following logic may apply: (Just don't tell anyone that I told you this :-)
Apple is not releasing any new versions of System 6 so no new come-from
patches will be forthcoming for System 6. If you do careful testing of the
traps you wish to tail-patch you will probably be OK. In general traps not
called from the ROMs, like Alert and MenuKey, will not contain come-from
patches.
One final thing: In system 7 it is safe to apply tail-patches to all traps
except FrontWindow.
--
[19] How do I patch a trap?
Here is some sample code for a head patch of Alert, a stack-based trap:
TrapPtr gOldAlertTrapAddress;
/****InstallPatch***************************************************/
void
InstallPatch (void)
{
gOldAlertTrapAddress = GetToolTrapAddress( _Alert );
SetToolTrapAddress( (long) AlertPatch, _Alert );
}
/****AlertPatch***************************************************/
pascal void
AlertPatch( short alertID, ProcPtr filterProcPtr )
{
SetUpA4();
MyAlert( alertID) ; //Do our thing
//store the correct alert addr
//in A0 while we can still access globals via A4
asm { move.l gOldAlertTrapAddress, A0 }
RestoreA4();
asm {
unlk A6 //match the link generated by C
jmp (A0) //jump to _Alert
}
}
There are a number of details to note here. This patch is of course part
of a code resource that is loaded at INIT time and detached as described in
an earlier section. The routine InstallPatch must be called at INIT time.
The routines GetToolTrapAddress and SetToolTrapAddress allow you to get and
set the addresses of Tool Traps. The similar routines GetOSTrapAddress and
SetOSTrapAddress allow you to manipulate the addresses of OS traps. The
routines NGetTrapAddress and NSetTrapAddress allow you to manipulate the
addresses of either, although they call glue code. I recommend that you
use the GetXTrapAddress and SetXTrapAddress calls. There are two obsolete
calls: GetTrapAddress and SetTrapAddress. Don't use them.
The prototype for this patch is declared as 'pascal void' while the
prototype for Alert is 'pascal short'. Because this is a head patch it
will not be returning a result; the result will be returned from the real
Alert trap. The pascal keyword is used to indicate pascal calling
conventions. It is not strictly required in all cases but does no harm.
The Think C routines SetUpA4 and RestoreA4 are called to allow access to
global variables by A4 addressing. In this case gOldAlertTrapAddress is
the only global variable we are addressing, unless any are used inside
MyAlert. Note that this variable is moved to A0 while access to global
variables is still available. In some cases one might move a global
variable to a local variable, which doesn't rely on A4 addressing. A4
could then be restored and the old trap address could be loaded into A0
later in the code.
Because there is a parameter list the compiler generates a Link A6
instruction at the start of this function. In order to restore the stack a
matching Unlk A6 must be placed at the end of the function. Since we are
exiting by the jmp (A0) we must insert the Unlk A6 ourselves. The compiler
does generate an Unlk A6 and an RTS at the end of this function, but they
will never be executed. The presence of the Link A6 instruction is
dependent on the particular compiler you use and on its rules for
generating a stack frame. It is a good idea to disassemble the code for
your trap patches to see whether a stack frame has been generated in order
to determine if you need to insert the Unlk A6 instruction. If you don't
match the Link A6 with an Unlk A6 the stack will be screwed up.
It is essential that the stack look exactly the same on exit from a head
patch as it does on entry. If not you will surely crash. (If you had good
reason you could modify the value of a parameter on the stack, but that's
another story.) This patch saves and restores A4 but does modify A0 and A1
(SetUpA4 uses A1). In general, with head patches of stack-based traps you
can modify A0, A1, D0, D1, and D2, but not any other registers. You may
need to look at the disassembled code to be sure that all your registers
are properly saved and restored.
The design of the patch as shown here, with the patch code calling a
separate function to perform the actual functionality of the patch is a
good design to follow with all but the simplest of patches.
You might think that you need to call StripAddress on the address of the
patch routine before passing this address to SetToolTrapAddress. This is
not necessary unless the address is actually a handle. If you were to load
a code resource and pass its entry point to SetToolTrapAddress then it
would need to be stripped.
--
[20] How do I patch a register-based trap?
Here is the code for a sample register-based trap patch:
TrapPtr gMountVolAddress;
/****InstallPatch**************************************************/
void
InstallPatch(void)
{
gMountVolAddress = GetOSTrapAddress( _MountVol );
SetOSTrapAddress( (long) MountVolPatch, _MountVol );
}
/****MountVolPatch**************************************************
This is a register-based trap that has A0 set to point to its parameter
block on entry. The prototype for MountVol is:
pascal OSErr PBMountVol( ParmBlkPtr paramBlock )
This patch beeps when a floppy or CD-ROM is inserted or when a
harddrive is mounted by the Finder.
*******************************************************************/
pascal void
MountVolPatch(void)
{
//Save some registers
//Save A0 since it's trashed by SetUpA4
//D1 contains the trap word
asm { movem.l a0/d0-d1, -(sp) }
SetUpA4(); //Allow access to global variables
SysBeep( 5 ); //The guts of our head patch
//store the correct MountVol addr
//in A1 while we can still access globals via A4
asm { move.l gMountVolAddress, A1 }
RestoreA4(); //Restore previous value in A4
asm { //Restore the registers
movem.l (sp)+, a0/d0-d1
jmp (A1) //jump to _MountVol
}
}
This head patch is similar in structure to the patch to Alert with a few
differences. The prototype uses no parameters and has no return value. The
single parameter is passed through A0. This patch doesn't do anything with
this value but it could be moved to a local variable and then used to
reference the fields in the parameter block if desired. Access to global
variables is by the same A4 mechanism as in the Alert patch. Note that
_MountVol is an OS trap so GetOSTrapAddress and SetOSTrapAddress are used
to set up the patch.
Since A0 is used to pass the parameter to this trap we jump to the real
_MountVol trap through A1.
Obviously A0 must be saved and restored in this patch. OS traps expect to
find the trap word in D1 so it must be saved and restored as well. Three
registers, A0, D0, and D1, are saved onto the stack with the movem
instruction, and restored at the end of the patch.
Think C doesn't generate a 'Link A6' at the start of this function because
there are no parameters and no local variables. Because of this no 'Unlk
A6' is needed at the end of the function.
--
[21] Can you show me a tail patch?
Here is another example of a patch to a register-based trap. This sample
is a tail patch and is dependent on the CodeWarrior environment. Because
CW allows you to specify that parameters are passed in registers this trap
patch requires no assembly.
extern pascal OSErr (*Old_MountVol)( ParmBlkPtr pb : __A0 ) : __D0;
pascal OSErr My_MountVol( ParmBlkPtr pb : __A0 ) : __D0
{
OSErr err;
long saveA4 = SetCurrentA4();
err = Old_MountVol( pb );
DoSomthingFunc();
SetA4( saveA4 );
return err;
}
--
[22] How do I patch a selector-based trap?
Here is a sample patch to PrGlue. This trap is the front end for all the
Printing Manager routines. Its selector is pushed on the stack
typedef struct
{
long Selector;
THPrint hPrint;
} PrJobDialogStack;
TrapPtr PrGlueAddress;
/****InstallPatch****************************************************/
void
InstallPatch(void)
{
PrGlueAddress = GetToolTrapAddress( _PrGlue );
SetToolTrapAddress( (long) PrGluePatch, _PrGlue );
}
/****PrGluePatch****************************************************
This is a stack-based trap with a long word selector also pushed
onto the stack. On entry the selector is at 4(A7). The return address is
at 0(A7). After the 'Link A6' the selector is at 12(A7).
*******************************************************************/
#define kSelectorOffset 12
#define kPrJobDialogSelector 0x32040488
pascal void
PrGluePatch(void)
{
PrJobDialogStack *StackPtr;
//Get address of the stack frame
//and save it in a local variable
asm {
lea kSelectorOffset(A7), A0
move.l A0, StackPtr
}
SetUpA4(); //Allow access to global variables
//Check the selector
if ( StackPtr->Selector == kPrJobDialogSelector )
{
SysBeep( 5 );
//Pass hPrint to our function to do something
DoSomethingFunc( StackPtr->hPrint );
}
//Store the correct PrGlue addr
//in A0 while can still access globals via A4
asm { move.l PrGlueAddress, A0 }
RestoreA4(); //Restore previous value in A4
asm {
unlk A6 //match C's Link A6
jmp (A0) //jump to _PrGlue
}
}
In order to access the selector and the parameters for PrGlue we use a
pointer to a struct. Once the pointer is initialized correctly we can
access the selector and any parameters from C easily.
According to NIM: PPC System Software it is not safe to patch
selector-based traps with PPC native code. All patches of selector-based
traps on the PowerMac should be written in 68K code.
--
[23] How do I patch a trap on the PPC?
See NIM 'PowerPC System Software' for a more complete discussion. There is
also a new book by Tom Thomson called 'Power Macintosh Programming Starter
Kit' that has examples of how to patch traps on the PowerMac.
Patching traps on the PowerMac is similar to patching on the 68K
architecture. Of course you must generate a UniversalProcPtr for each of
your patches in the system heap, and these are then passed to the
SetXTrapAddress routines. Since code fragments have their own globals the
use of A4 or A5-based mechanisms for accessing global variables isn't
needed. In order to call the previous trap you need to call
CallUniversalProc or CallOSTrapUniversalProc and return its result from
your patch. As a result all patches on the PowerMac are tail patches.
You cannot safely patch a selector-based trap in native code. See NIM PPC
for an explanation of this. Unless or until this changes you shoud do all
patching of selector-based traps in 68K code.
You may find an application called 'Traps Check' useful. This app supplies
a report about all the traps on a Powermac, indicating whether each trap is
emulated or native. Another way to do this is to drop into MacsBug and
disassemble from the address of the trap you're interested in (e.g., 'il
CopyBits' ). For traps that are native you'll see a routine descriptor
that begins with the MixedModeMagic trap (AAFE). This of course won't tell
you if the trap has been patched. You can identify a patch by whether it's
in RAM or ROM, from its address. Determining whether a patched trap is PPC
native or not may take some additioinal sleuthing. You can find Traps Check
at: ftp://sumex-aim.stanford.edu/info-mac/dev/traps-check-10.hqx
Here is a sample PowerMac trap patch for GetResource:
enum {
uppGetResourceProcInfo= kPascalStackBased
| RESULT_SIZE(SIZE_CODE(sizeof(Handle)))
| STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(ResType)))
| STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(short)))
};
UniversalProcPtr gGetResourceUPP;
UniversalProcPtr gGetResourcePatchUPP;
/****InstallPatch**************************************************/
void
InstallPatch(void)
{
THz saveZone = GetZone();
SetZone( SystemZone() ); //put UPP in syszone
gGetResourceUPP = GetToolTrapAddress( _GetResource );
gGetResourcePatchUPP =
NewRoutineDescriptor( (ProcPtr) GetResourcePatch,
uppGetResourceProcInfo, GetCurrentISA() );
SetToolTrapAddress( gGetResourcePatchUPP, _GetResource );
SetZone( saveZone );
}
/****GetResourcePatch*********************************************/
Handle
GetResourcePatch( ResType theType, short theID )
{
Handle result;
//We don't need no 'DUMB' resources
if ( theType == 'DUMB' )
result = NULL;
else
result = (Handle) CallUniversalProc( gGetResourceUPP,
uppGetResourceProcInfo, theType, theID );
return result;
}
This patch as written is PPC only for illustrative purposes. In real life
the NewRoutineDescriptor and CallUniversalProc would be #defined in a
header file and would compile correctly for both 68K and PPC code. You can
find examples of how to do this in the Universal header files.
--
[24] Can I write a fat trap?
//Under construction
--
[25] Tips?
If your patch isn't called you may have guessed wrong on whether it's a
ToolTrap or an OSTrap. The high bit of the second byte of the trap word is
set for ToolTraps. The following function can be used to get the correct
trap address for both ToolTraps and OSTraps.
pascal void * GetCurrentTrapAddress( unsigned short trapWord )
{
if ( trapWord & 0x0800 )
return GetToolTrapAddress ( trapWord & 0x07FF );
else
return GetOSTrapAddress ( trapWord & 0x07FF );
}
If the machine crashes after leaving your patch you have probably munged
the stack or not saved and restored all the registers that you must.
The Finder patches a number of traps when it loads in a way that prevents
earlier trap patches from functioning. If your patch doesn't appear to be
called it may be one of these patches.
--
[26] What other sources of information are available?
Knaster 'How to Write Macintosh Software'
Knaster and Rollin, 'Macintosh Programming Secrets'. These books on Mac
programming has some excellent info on trap patching.
Tom Thomson 'Power Macintosh Programming Starter Kit' This book has some
example code for writing trap patches and extensions on the PowerMac.
The Extension Shell package (by Dair Grant, dair@kagi.com) at:
ftp://sumex-aim.stanford.edu/info-mac/dev/src/extension-shell-15.hqx
Usenet Macintosh Programmers Guide
ftp://sumex-aim.stanford.edu/info-mac/dev/info/usenet-mac-prog-guide-msw.hqx
All of the Apple Tech Notes have been made available on Apple's web server:
http://www.info.apple.com/dev/technotes/Main.html